{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "DkA0Fobtb9dM"
   },
   "source": [
    "##### Copyright 2020 The Cirq Developers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "cellView": "form",
    "id": "tUshu7YfcAAW"
   },
   "outputs": [],
   "source": [
    "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
    "# you may not use this file except in compliance with the License.\n",
    "# You may obtain a copy of the License at\n",
    "#\n",
    "# https://www.apache.org/licenses/LICENSE-2.0\n",
    "#\n",
    "# Unless required by applicable law or agreed to in writing, software\n",
    "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
    "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
    "# See the License for the specific language governing permissions and\n",
    "# limitations under the License."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "igOQCrBOcF5d"
   },
   "source": [
    "# Protocols"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "LHRAvc9TcHOH"
   },
   "source": [
    "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
    "  <td>\n",
    "    <a target=\"_blank\" href=\"https://www.example.org/cirq/protocols\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" />View on QuantumLib</a>\n",
    "  </td>\n",
    "  <td>\n",
    "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/quantumlib/Cirq/blob/master/docs/protocols.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />Run in Google Colab</a>\n",
    "  </td>\n",
    "  <td>\n",
    "    <a target=\"_blank\" href=\"https://github.com/quantumlib/Cirq/blob/master/docs/protocols.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" />View source on GitHub</a>\n",
    "  </td>\n",
    "  <td>\n",
    "    <a href=\"https://storage.googleapis.com/tensorflow_docs/Cirq/docs/protocols.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\" />Download notebook</a>\n",
    "  </td>\n",
    "</table>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "id": "bd9529db1c0b"
   },
   "outputs": [],
   "source": [
    "try:\n",
    "    import cirq\n",
    "except ImportError:\n",
    "    print(\"installing cirq...\")\n",
    "    !pip install --quiet cirq\n",
    "    print(\"installed cirq.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "lB__WndjHWGa"
   },
   "source": [
    "# Introduction\n",
    "\n",
    "Cirq's protocols are very similar concept to Python's built-in protocols that were introduced in [PEP 544](https://www.python.org/dev/peps/pep-0544/).\n",
    "Python's built-in protocols are extremely convenient, for example behind all the for loops and list comprehensions you can find the Iterator protocol.\n",
    "As long as an object has the `__iter__()` magic method that returns an iterator object, it has iterator support.\n",
    "An iterator object has to define `__iter__()` and `__next__()` magic methods, that defines the iterator protocol.\n",
    "The `iter(val)` builtin function returns an iterator for `val` if it defines the above methods, otherwise throws a `TypeError`. Cirq protocols work similarly.\n",
    "\n",
    "A canonical Cirq protocol example is the `unitary` protocol that allows to check the unitary matrix of values that support the protocol by calling `cirq.unitary(val)`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "4a6bcd71ae5f"
   },
   "outputs": [],
   "source": [
    "import cirq \n",
    "\n",
    "print(cirq.X)\n",
    "print(\"cirq.X unitary:\\n\", cirq.unitary(cirq.X))\n",
    "\n",
    "a, b = cirq.LineQubit.range(2)\n",
    "circuit = cirq.Circuit(cirq.X(a), cirq.Y(b))\n",
    "print(circuit)\n",
    "print(\"circuit unitary:\\n\", cirq.unitary(circuit))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "6b3b43b2141b"
   },
   "source": [
    "When an object does not support a given protocol, an error is thrown."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "a988c0efc9b7"
   },
   "outputs": [],
   "source": [
    "try: \n",
    "    print(cirq.unitary(a)) ## error!\n",
    "except Exception as e: \n",
    "    print(\"As expected, a qubit does not have a unitary. The error: \")\n",
    "    print(e)\n",
    "    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "b4d4bc702a5e"
   },
   "source": [
    "## What is a protocol? \n",
    "\n",
    "A protocol is a combination of the following two items: \n",
    "- a `SupportsXYZ` class, which defines and documents all the magic functions that need to be implemented in order to support that given protocol \n",
    "- the entrypoint function(s), which are exposed to the main cirq namespace as `cirq.xyz()`\n",
    "\n",
    "Note: While the protocol is technically both of these things, we refer to the public utility functions interchangeably as protocols. See the list of them below.\n",
    "\n",
    "\n",
    "## Cirq's protocols\n",
    "\n",
    "For a complete list of Cirq protocols, refer to the `cirq.protocols` package. \n",
    "Here we provide a list of frequently used protocols for debugging, simulation and testing.\n",
    "\n",
    "\n",
    "| Protocol | Description | \n",
    "|----------|-------|\n",
    "|`cirq.apply_channel`| High performance evolution under a channel evolution. |\n",
    "|`cirq.apply_mixture`| High performance evolution under a mixture of unitaries evolution. |\n",
    "|`cirq.apply_unitaries`| Apply a series of unitaries onto a state tensor. |\n",
    "|`cirq.apply_unitary`| High performance left-multiplication of a unitary effect onto a tensor. |\n",
    "|`cirq.approx_eq`| Approximately compares two objects. |\n",
    "|`cirq.channel`| Returns a list of matrices describing the channel for the given value. |\n",
    "|`cirq.commutes`| Determines whether two values commute. |\n",
    "|`cirq.definitely_commutes`| Determines whether two values definitely commute. |\n",
    "|`cirq.decompose`| Recursively decomposes a value into `cirq.Operation`s meeting a criteria. |\n",
    "|`cirq.decompose_once`| Decomposes a value into operations, if possible. |\n",
    "|`cirq.decompose_once_with_qubits`| Decomposes a value into operations on the given qubits. |\n",
    "|`cirq.equal_up_to_global_phase`| Determine whether two objects are equal up to global phase. |\n",
    "|`cirq.has_channel`| Returns whether the value has a channel representation. |\n",
    "|`cirq.has_mixture`| Returns whether the value has a mixture representation. |\n",
    "|`cirq.has_unitary`| Determines whether the value has a unitary effect. |\n",
    "|`cirq.inverse`| Returns the inverse `val**-1` of the given value, if defined. |\n",
    "|`cirq.is_measurement`| Determines whether or not the given value is a measurement. |\n",
    "|`cirq.is_parameterized`| Returns whether the object is parameterized with any Symbols. |\n",
    "|`cirq.measurement_key`| Get the single measurement key for the given value. |\n",
    "|`cirq.measurement_keys`| Gets the measurement keys of measurements within the given value. |\n",
    "|`cirq.mixture`| Return a sequence of tuples representing a probabilistic unitary. |\n",
    "|`cirq.num_qubits`| Returns the number of qubits, qudits, or qids `val` operates on. |\n",
    "|`cirq.parameter_names`| Returns parameter names for this object. |\n",
    "|`cirq.parameter_symbols`| Returns parameter symbols for this object. |\n",
    "|`cirq.pauli_expansion`| Returns coefficients of the expansion of val in the Pauli basis. |\n",
    "|`cirq.phase_by`| Returns a phased version of the effect. |\n",
    "|`cirq.pow`| Returns `val**factor` of the given value, if defined. |\n",
    "|`cirq.qasm`| Returns QASM code for the given value, if possible. |\n",
    "|`cirq.qid_shape`| Returns a tuple describing the number of quantum levels of each |\n",
    "|`cirq.quil`| Returns the QUIL code for the given value. |\n",
    "|`cirq.read_json`| Read a JSON file that optionally contains cirq objects. |\n",
    "|`cirq.resolve_parameters`| Resolves symbol parameters in the effect using the param resolver. |\n",
    "|`cirq.to_json`| Write a JSON file containing a representation of obj. |\n",
    "|`cirq.trace_distance_bound`| Returns a maximum on the trace distance between this effect's input |\n",
    "|`cirq.trace_distance_from_angle_list`| Given a list of arguments of the eigenvalues of a unitary matrix, |\n",
    "|`cirq.unitary`| Returns a unitary matrix describing the given value. |\n",
    "|`cirq.validate_mixture`| Validates that the mixture's tuple are valid probabilities. |\n",
    "\n",
    "\n",
    "### Quantum operator representation protocols\n",
    "\n",
    "The following family of protocols is an important and frequently used set of features of Cirq and it is worthwhile mentioning them and and how they interact with each other. They are, in the order of increasing generality:\n",
    "\n",
    "* `*unitary`\n",
    "* `*mixture`\n",
    "* `*channel`\n",
    "\n",
    "All these protocols make it easier to work with different representations of quantum operators, namely: \n",
    "- finding that representation (`unitary`, `mixture`, `channel`), \n",
    "- determining whether the operator has that representation (`has_*`) \n",
    "- and applying them (`apply_*`) on a state vector. \n",
    "\n",
    "#### Unitary \n",
    "\n",
    "The `*unitary` protocol is the least generic, as only unitary operators should implement it. The `cirq.unitary` function returns the matrix representation of the operator in the computational basis. We saw an example of the unitary protocol above, but let's see the unitary matrix of the Pauli-Y operator as well: \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "id": "d2ae567abe99"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[0.+0.j 0.-1.j]\n",
      " [0.+1.j 0.+0.j]]\n"
     ]
    }
   ],
   "source": [
    "print(cirq.unitary(cirq.Y))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "2c8e107b45da"
   },
   "source": [
    "#### Mixture \n",
    "\n",
    "The `*mixture` protocol should be implemented by operators that are _unitary-mixtures_. These probabilistic operators are represented by a list of tuples ($p_i$, $U_i$), where each unitary effect $U_i$ occurs with a certain probability $p_i$, and $\\sum p_i = 1$. Probabilities are a Python float between 0.0 and 1.0, and the unitary matrices are numpy arrays.\n",
    "\n",
    "Constructing simple probabilistic gates in Cirq is easiest with the `with_probability` method. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "id": "5f9ec1e69ba6"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "probability: 0.3\n",
      "operator:\n",
      "[[0.+0.j 1.+0.j]\n",
      " [1.+0.j 0.+0.j]]\n",
      "probability: 0.7\n",
      "operator:\n",
      "[[1. 0.]\n",
      " [0. 1.]]\n"
     ]
    }
   ],
   "source": [
    "probabilistic_x = cirq.X.with_probability(.3)\n",
    "\n",
    "for p, op in cirq.mixture(probabilistic_x):\n",
    "    print(f\"probability: {p}\")\n",
    "    print(\"operator:\")\n",
    "    print(op)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "51addeffe113"
   },
   "source": [
    "In case an operator does not implement `SupportsMixture`, but does implement `SupportsUnitary`, `*mixture` functions fall back to the `*unitary` methods. It is easy to see that a unitary operator $U$ is just a \"mixture\" of a single unitary with probability $p=1$. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "id": "4a8a43eb4cbc"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "((1.0, array([[0.+0.j, 0.-1.j],\n",
      "       [0.+1.j, 0.+0.j]])),)\n",
      "True\n"
     ]
    }
   ],
   "source": [
    "# cirq.Y has a unitary effect but does not implement SupportsMixture\n",
    "# thus mixture protocols will return ((1, cirq.unitary(Y)))\n",
    "print(cirq.mixture(cirq.Y))\n",
    "print(cirq.has_mixture(cirq.Y))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "7385326f4a37"
   },
   "source": [
    "#### Channel \n",
    "\n",
    "\n",
    "The `channel` representation is the operator sum representation of a quantum operator (a channel):  \n",
    "\n",
    "        $$\n",
    "        \\rho \\rightarrow \\sum_{k=0}^{r-1} A_k \\rho A_k^\\dagger\n",
    "        $$\n",
    "\n",
    "These matrices are required to satisfy the trace preserving condition\n",
    "\n",
    "        $$\n",
    "        \\sum_{k=0}^{r-1} A_k^\\dagger A_k = I\n",
    "        $$\n",
    "\n",
    "where $I$ is the identity matrix. The matrices $A_k$ are sometimes called Kraus or noise operators.\n",
    "    \n",
    "The `cirq.channel` method returns a tuple of numpy arrays, one for each of the Kraus operators:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "id": "dbfd41797730"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([[0.83666003, 0.        ],\n",
       "        [0.        , 0.83666003]]),\n",
       " array([[0.        +0.j, 0.31622777+0.j],\n",
       "        [0.31622777+0.j, 0.        +0.j]]),\n",
       " array([[0.+0.j        , 0.-0.31622777j],\n",
       "        [0.+0.31622777j, 0.+0.j        ]]),\n",
       " array([[ 0.31622777+0.j,  0.        +0.j],\n",
       "        [ 0.        +0.j, -0.31622777+0.j]]))"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cirq.channel(cirq.DepolarizingChannel(p=0.3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "6a0d509482eb"
   },
   "source": [
    "In case the operator does not implement `SupportsChannel`, but it does implement `SupportsMixture`, the `*channel` protocol will generate the Kraus operators based on the  `*mixture` representation. \n",
    "\n",
    "$$\n",
    "((p_0, U_0),(p_1, U_1),\\ldots,(p_n, U_n)) \\rightarrow (\\sqrt{p_0}U_0, \\sqrt{p_1}U_1, \\ldots, \\sqrt{p_n}U_n)\n",
    "$$\n",
    "\n",
    "Thus for example `((0.25, X), (0.75, I)) -> (0.5 X, sqrt(0.75) I)`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {
    "id": "259cadda9d9d"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([[0. +0.j, 0.5+0.j],\n",
       "        [0.5+0.j, 0. +0.j]]),\n",
       " array([[0.8660254, 0.       ],\n",
       "        [0.       , 0.8660254]]))"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cirq.channel(cirq.X.with_probability(0.25))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "288ad97dcd90"
   },
   "source": [
    "In the simplest case of a simple unitary operator, `cirq.channel` returns the exact unitary matrix as `cirq.unitary`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "id": "d424899d3af2"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(array([[0.+0.j, 0.-1.j],\n",
      "       [0.+1.j, 0.+0.j]]),)\n",
      "[[0.+0.j 0.-1.j]\n",
      " [0.+1.j 0.+0.j]]\n",
      "True\n"
     ]
    }
   ],
   "source": [
    "print(cirq.channel(cirq.Y))\n",
    "print(cirq.unitary(cirq.Y))\n",
    "print(cirq.has_channel(cirq.Y))"
   ]
  }
 ],
 "metadata": {
  "colab": {
   "collapsed_sections": [],
   "name": "protocols.ipynb",
   "toc_visible": true
  },
  "kernelspec": {
   "display_name": "Python 3",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
